module.exports = function () {
    const DatabasePersistence = {
        db: null,
        info: {
            name: "ada",
            version: 1,
            store: "ada-source",
            key: "name",
            value: "source"
        },
        ready() {
            if (!this.db) {
                if (!self.indexedDB) {
                    self.indexedDB = self.mozIndexedDB || self.webkitIndexedDB;
                }
                return new Promise(resolve => {
                    let request = self.indexedDB.open(this.info.name, this.info.version);
                    request.onupgradeneeded = () => {
                        let db = request.result;
                        if (db.objectStoreNames.contains(this.info.store)) {
                            db.deleteObjectStore(this.info.store);
                        }
                        let store = db.createObjectStore(this.info.store, { keyPath: this.info.key });
                        store.put({ name: this.info.value, data: {} });
                    };
                    request.onsuccess = () => {
                        this.db = request.result;
                        resolve();
                    };
                    request.onerror = () => resolve();
                });
            } else {
                return Promise.resolve();
            }
        },
        getAll() {
            return new Promise(resolve => {
                this.ready().then(() => {
                    let transaction = this.db.transaction([this.info.store], "readwrite");
                    let request = transaction.objectStore(this.info.store).get(this.info.value);
                    transaction.onerror = () => resolve({});
                    request.onerror = () => resolve({});
                    request.onsuccess = () => {
                        resolve(request.result ? request.result.data : {});
                    };
                });
            });
        },
        saveAll(data) {
            return new Promise(resolve => {
                this.ready().then(() => {
                    let transaction = this.db.transaction([this.info.store], "readwrite");
                    transaction.oncomplete = () => resolve();
                    transaction.onerror = () => resolve();
                    transaction.objectStore(this.info.store).put({
                        name: this.info.value,
                        data: data
                    });
                });
            });
        },
        clean({ apps }) {
            this.getAllManifest().then(data => {
                let rManifest = {};
                Reflect.ownKeys(data).forEach(key => {
                    let info = data[key];
                    let r = apps.find(a => {
                        return a.name === key && a.version === info.version;
                    });
                    if (r) {
                        rManifest[key] = info;
                    }
                });
                let keys = [];
                Reflect.ownKeys(rManifest).forEach(name => {
                    let data = rManifest[name];
                    let { map } = data;
                    keys = keys.concat(Reflect.ownKeys(map).filter(a => a !== 'packages').map(a => map[a]));
                });
                return { keys, manifest: rManifest };
            }).then(({ keys, manifest }) => {
                return this.getAll().then(data => {
                    let r = {};
                    Reflect.ownKeys(data).forEach(a => {
                        if (keys.includes(a)) {
                            r[a] = data[a];
                        }
                    });
                    return { source: r, manifest };
                });
            }).then(({ source, manifest }) => {
                return this.saveAll(source).then(() => this.setAllManifest(manifest));
            });
        },
        getAllManifest() {
            return new Promise(resolve => {
                this.ready().then(() => {
                    let transaction = this.db.transaction([this.info.store], "readwrite");
                    let request = transaction.objectStore(this.info.store).get('manifest');
                    transaction.onerror = () => resolve({});
                    request.onerror = () => resolve({});
                    request.onsuccess = () => {
                        resolve(request.result ? request.result.data : {});
                    };
                });
            });
        },
        setAllManifest(data) {
            return new Promise(resolve => {
                this.ready().then(() => {
                    let transaction = this.db.transaction([this.info.store], "readwrite");
                    transaction.oncomplete = () => resolve();
                    transaction.onerror = () => resolve();
                    transaction.objectStore(this.info.store).put({
                        name: 'manifest',
                        data
                    });
                });
            });
        },
        getManifest({ name, version }) {
            return this.getAllManifest().then(t => {
                if (t[name] && t[name].version === version) {
                    return t[name];
                } else {
                    return null;
                }
            });
        },
        setManifest({ data }) {
            return new Promise(resolve => {
                this.ready().then(() => this.getAllManifest()).then(all => {
                    let transaction = this.db.transaction([this.info.store], "readwrite");
                    transaction.oncomplete = () => resolve();
                    transaction.onerror = () => resolve();
                    all[data.name] = data;
                    transaction.objectStore(this.info.store).put({
                        name: 'manifest',
                        data: all
                    });
                });
            });
        }
    }

    const Queue = {
        _list: [],
        _run: false,
        add(fn) {
            return new Promise(resolve => {
                this._list.push({ resolve, fn });
                this._trigger();
            });
        },
        _trigger() {
            if (!this._run) {
                if (this._list.length > 0) {
                    this._run = true;
                    let { resolve, fn } = this._list.shift();
                    Promise.resolve().then(() => fn()).then(a => {
                        this._run = false;
                        resolve(a);
                        this._trigger();
                    }, e => {
                        console.log('queue emit error:', e);
                        this._run = false;
                        resolve(e);
                        this._trigger();
                    });
                } else {
                    this._run = false;
                }
            }
        }
    }

    onmessage = ({ data }) => {
        let { id } = data;
        return Queue.add(() => {
            return DatabasePersistence[data.data.type](data.data.data);
        }).then(data => {
            postMessage({ id, data });
        });
    }
};